const { exec }   = require('child_process');
const log        = require('../util/log');
const config     = require('../crawler/config/config');
const path       = require('path');
const jedis      = require('../util/jedis');
const Const      = require('../common/const');
const os         = require('os');

const scriptPath = path.join(__dirname,'../common/check_and_create.sh');
const existCode  = 1;
const sign       = '$1';

class Cmd_handler{

    constructor() {
        if(!Cmd_handler.single){
            log.info(`constructor cmdHandler`);
            this._PROCESS_CMD = {
                //检查远程服务器是否存在文件夹或者文件,不存在则创建
                checkAndCreateDirCmd:`sh ${scriptPath} ${config.PUSH.scp.host} $1 ${config.PUSH.scp.port}`,
                //scp拷贝文件至远程服务器
                scpCmd:`scp -P ${config.PUSH.scp.port} $1 root@${config.PUSH.scp.host}:$2`,
                //远程创建目录
                createCmd:'',
                //$1(原始ts路径) $2(输出文件路径) $3(IV值) $4(16进制秘钥值)
                decryptCmd:`openssl aes-128-cbc -d -in $1 -out $2 -nosalt -iv $3 -K $4`,
                //秘钥文件转16进制
                hexTransCmd:`cat $1 | hexdump -e '16/1 "%02x" "\\n"'`
            };
            //根据系统初始化脚本参数
            log.info(`System：${os.type}`);
            switch (os.type()) {
                case 'Windows_NT':
                    this._PROCESS_CMD.createCmd = `ssh -p ${config.PUSH.scp.port} root@${config.PUSH.scp.host} \`mkdir ${sign}\``;
                    break;
                case 'Darwin':
                    break;
                case 'Linux':
                    this._PROCESS_CMD.createCmd = `ssh -p ${config.PUSH.scp.port} root@${config.PUSH.scp.host} 'mkdir ${sign}'`;
                    break;
                default:
                    log.warn(`Not Unrecognized System. Type:${os.type}`);
            }
            Cmd_handler.single = this;
        }
        return Cmd_handler.single;
    }

    /**
     * 检查远程目录是否存在 不存在则创建
     * @param fileDir 远程文件路径
     * @param needCreate 不存在时,是否需要创建
     * @returns {Promise<unknown>}
     */
    checkAndCreateDir(fileDir,needCreate){
        return new Promise((async (resolve, reject) => {
            let isExist = await this.checkDirIsExistByCache(fileDir);
            log.debug(`[${fileDir}] already exist? : [${isExist}]`);
            if(isExist){
                log.info(`Dir [${fileDir}] already cache in redis .`);
                resolve(true);
                return;
            }
            let script = this._PROCESS_CMD.checkAndCreateDirCmd;
            script = script.replace(sign,fileDir);

            this.execCmd(script)
                .then((eRes) => {
                    log.info(`Cmd result info:${JSON.stringify(eRes)}`);
                    if(!eRes.status){
                        reject(eRes.message);
                        return;
                    }
                    if(existCode === parseInt(eRes.stdout)){
                        log.info(`Directory:[${fileDir}] already exist.`);
                        resolve();
                        return;
                    }
                    log.info(`Directory:[${fileDir}] not exist.`);
                    if(!needCreate){
                        resolve();
                        return;
                    }
                    //如果需要创建
                    let createScript = this._PROCESS_CMD.createCmd;
                    createScript = createScript.replace(sign,fileDir);

                    this.execCmd(createScript)
                        .then(async (eRes) => {
                            if(!eRes.status){
                                reject(eRes.message);
                                return;
                            }
                            log.info(`Remote create directory：[${fileDir}] successful.`);
                            await this.cacheDirCreate(fileDir);
                            resolve(true);
                        }).catch((e) => {
                            reject(e.message)
                        })
                }).catch((e) => {
                    log.err(`Check front cmd execute failed. message:${e.message}`);
                    reject(e.message);
                });
        }))

    }

    /**
     * 从redis缓存中检查文件目录是否存在
     * @returns {Promise<any|Thenable<any>>}
     */
    async checkDirIsExistByCache(dirPath){
        let redis = new jedis();
        return await redis.sismember(Const._DIR_EXIST_CACHE_KEY,dirPath);
    }

    /**
     * 缓存已经创建过的文件目录
     * @returns {Promise<void>}
     */
    async cacheDirCreate(dirPath){
        let redis = new jedis();
        await redis.sadd(Const._DIR_EXIST_CACHE_KEY,dirPath)
    }

    /**
     * 执行脚本
     * @param script
     */
    execCmd(script){
        return new Promise(((resolve, reject) => {
            exec(script, {timeout:config.CMD.timeout},(err, stdout, stderr) => {
                if(err){
                    log.err(`Execute remotely script:[${script}] filed. errMessage:${err.message}`);
                    resolve({
                        message:'failed',
                        status:false,
                        stdout:stdout.replace('\n','')
                    });
                    return;
                }
                log.info(`Execute remotely script:[${script}] successful. stdout:${stdout}`);
                resolve({
                    message:'',
                    status:true,
                    stdout:stdout.replace('\n','')
                });
            })
        }));
    }

    /**
     * 推送文件
     * @param filesPath 本地文件路径集合
     * @param pushDir 要推送到的远程服务器目录
     * @param retry 重试次数
     */
    pushFile(filesPath,pushDir,retry = 0){
        return new Promise((async (resolve, reject) => {
            retry ++;
            //检查文件夹是否存在
              this.checkAndCreateDir(pushDir,true)
                .then(() => {
                    let scpScript = this._PROCESS_CMD.scpCmd;
                    let filePathStr = ' ';
                    for(let filePath of filesPath){
                        filePathStr += filePath + ' ';
                    }
                    scpScript = scpScript.replace('$1',filePathStr);
                    scpScript = scpScript.replace('$2',pushDir);
                    this.execCmd(scpScript)
                        .then((opt) => {
                            if(retry > 3){
                                throw new Error(`Retry Max Cnt:[3]`);
                            }
                            return opt.status ? resolve() : this.pushFile(filesPath,pushDir,retry);
                        }).catch((e) => {
                            log.err(`Scp command failed. e:${e.message}`);
                            reject(e);
                        })
                }).catch((e) => {
                    log.info(`Cmd execute failed:${e.message}`);
                })
        }))
    }

}
Cmd_handler.single = null;
module.exports = Cmd_handler;
